package com.hqd.ch03.v51.beans.support;

import com.hqd.ch03.v51.beans.BeansException;
import com.hqd.ch03.v51.beans.factory.BeanInitializationException;
import com.hqd.ch03.v51.beans.factory.config.ConfigurableListableBeanFactory;
import com.hqd.ch03.v51.beans.factory.config.PlaceholderConfigurerSupport;
import com.hqd.ch03.v51.config.StringValueResolver;
import com.hqd.ch03.v51.context.EnvironmentAware;
import com.hqd.ch03.v51.core.env.*;

import java.io.IOException;
import java.util.Properties;

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {

    /**
     * {@value} is the name given to the {@link PropertySource} for the set of
     * {@linkplain #mergeProperties() merged properties} supplied to this configurer.
     */
    public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";

    /**
     * {@value} is the name given to the {@link PropertySource} that wraps the
     * {@linkplain #setEnvironment environment} supplied to this configurer.
     */
    public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";


    private MutablePropertySources propertySources;


    private PropertySources appliedPropertySources;


    private Environment environment;


    /**
     * Customize the set of {@link PropertySources} to be used by this configurer.
     * <p>Setting this property indicates that environment property sources and
     * local properties should be ignored.
     *
     * @see #postProcessBeanFactory
     */
    public void setPropertySources(PropertySources propertySources) {
        this.propertySources = new MutablePropertySources(propertySources);
    }

    /**
     * {@code PropertySources} from the given {@link Environment}
     * will be searched when replacing ${...} placeholders.
     *
     * @see #setPropertySources
     * @see #postProcessBeanFactory
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }


    /**
     * Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
     * against this configurer's set of {@link PropertySources}, which includes:
     * <ul>
     * <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
     * environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
     * <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
     * {@linkplain #setLocations have} {@linkplain #setProperties been}
     * {@linkplain #setPropertiesArray specified}
     * <li>any property sources set by calling {@link #setPropertySources}
     * </ul>
     * <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
     * ignored</strong>. This method is designed to give the user fine-grained control over property
     * sources, and once set, the configurer makes no assumptions about adding additional sources.
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            if (this.environment != null) {
                PropertyResolver propertyResolver = this.environment;
                // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a
                // local PropertyResolver to enforce that setting, since the Environment is most
                // likely not configured with ignoreUnresolvablePlaceholders set to true.
                // See https://github.com/spring-projects/spring-framework/issues/27947
                if (this.ignoreUnresolvablePlaceholders && (this.environment instanceof ConfigurableEnvironment)) {
                    ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) this.environment;
                    PropertySourcesPropertyResolver resolver =
                            new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());
                    resolver.setIgnoreUnresolvableNestedPlaceholders(true);
                    propertyResolver = resolver;
                }
                PropertyResolver propertyResolverToUse = propertyResolver;
                this.propertySources.addLast(
                        new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
                            @Override

                            public String getProperty(String key) {
                                return propertyResolverToUse.getProperty(key);
                            }
                        }
                );
            }
            try {
                PropertySource<?> localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
                if (this.localOverride) {
                    this.propertySources.addFirst(localPropertySource);
                } else {
                    this.propertySources.addLast(localPropertySource);
                }
            } catch (IOException ex) {
                throw new BeanInitializationException("Could not load properties", ex);
            }
        }

        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        this.appliedPropertySources = this.propertySources;
    }

    /**
     * Visit each bean definition in the given bean factory and attempt to replace ${...} property
     * placeholders with values from the given properties.
     */
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
                                     final ConfigurablePropertyResolver propertyResolver) throws BeansException {

        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);

        StringValueResolver valueResolver = strVal -> {
            String resolved = (this.ignoreUnresolvablePlaceholders ?
                    propertyResolver.resolvePlaceholders(strVal) :
                    propertyResolver.resolveRequiredPlaceholders(strVal));
            if (this.trimValues) {
                resolved = resolved.trim();
            }
            return (resolved.equals(this.nullValue) ? null : resolved);
        };

        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

    /**
     * Implemented for compatibility with
     * {@link org.springframework.beans.factory.config.PlaceholderConfigurerSupport}.
     *
     * @throws UnsupportedOperationException in this implementation
     * @deprecated in favor of
     * {@link #processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver)}
     */
    @Override
    @Deprecated
    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) {
        throw new UnsupportedOperationException(
                "Call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver) instead");
    }

    /**
     * Return the property sources that were actually applied during
     * {@link #postProcessBeanFactory(ConfigurableListableBeanFactory) post-processing}.
     *
     * @return the property sources that were applied
     * @throws IllegalStateException if the property sources have not yet been applied
     * @since 4.0
     */
    public PropertySources getAppliedPropertySources() throws IllegalStateException {
        return this.appliedPropertySources;
    }

}
